package springapp.dao;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.hibernate.Criteria;
import org.hibernate.SessionFactory;
import org.hibernate.classic.Session;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Projections;
import org.springframework.beans.factory.annotation.Autowired;

import springapp.util.IEntitySetter;
import springapp.util.ObjectTypeCaster;
import springapp.util.Utils;

public abstract class HibernateDao<E, ID extends Serializable> implements
		GenericDao<E, Serializable> {

	public static String TABLE_ALIAS = "t";

	@Autowired
	private SessionFactory sessionFactory;

	/**
	 * A hack to let use dao during spring intialization
	 * directly use openSession to get a session
	 */
	private Session initSession = null;
	private boolean initialized = true;

	/**
	 * holds the entity class which is to be saved
	 */
	private Class<E> persistentClass;

	@SuppressWarnings("unchecked")
	public HibernateDao() {
		this.persistentClass = (Class<E>) ((ParameterizedType) getClass()
				.getGenericSuperclass()).getActualTypeArguments()[0];
	}

	/**
	 * keeping it protected for testing purpose
	 * @return
	 */
	protected Session getSession() {
		if (!initialized) {
			return initSession;
		}
		return  this.sessionFactory.getCurrentSession();
	}

	@SuppressWarnings("unchecked")
	public E getEntity(Serializable id) {
		return (E) getSession().get(persistentClass, id);
	}

	@SuppressWarnings("unchecked")
	public List<E> getEntities(List<Serializable> idList) {
		List<E> entities = new ArrayList<E>();
		Session session = getSession();
		for (Serializable id : idList) {
			E savedEntity = (E) session.get(persistentClass, id);
			entities.add((E) savedEntity);
		}
		return entities;
	}

	public void saveEntity(E entity) {
		getSession().save(entity);
	}

	public void saveEntities(E... entities) {
		Session session = getSession();
		for (E e : entities) {
			session.save(e);
		}

	}

	public List<E> getAllEntities() {
		return findByCriteria();
	}

	public void updateEntity(E entity) {
		getSession().update(entity);
	}

	@SuppressWarnings("unchecked")
	public List<E> findByExample(E exampleInstance) {
		Criteria crit = getSession().createCriteria(persistentClass);
		Example example = Example.create(exampleInstance);
		crit.add(example);
		return crit.list();
	}

	public List<E> findByCriteria(Criterion... criterion) {
		return findByCriteria(buildCriteria(criterion));
	}

	@SuppressWarnings("unchecked")
	public List<E> findByCriteria(Criteria criterion) {
		return criterion.list();
	}

	private Criteria buildCriteria(Criterion... criterion) {
		Criteria crit = getSession().createCriteria(persistentClass);
		for (Criterion c : criterion) {
			crit.add(c);
		}
		return crit;
	}

	private ProjectionList buildProjection(String[] properties) {
		ProjectionList proList = Projections.projectionList();
		for (String propertyName : properties) {
			proList.add(Projections.property(propertyName));

		}
		return proList;
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	/**
	 * entitySetters has set methods which will set the relevant field
	 * columns,entitySetters and typeCasters have to be in order.
	 * @param columns
	 * @param criterion
	 * @param entitySetters
	 * @param typeCasters
	 * @param entityGenerator
	 * @return
	 */
	public List<E> getColumns(String[] columns, Criterion[] criterion,
			IEntitySetter[] entitySetters, ObjectTypeCaster[] typeCasters,
			EntityGenerator<E> entityGenerator) {
		Criteria criteria = buildCriteria(criterion);
		criteria.setProjection(buildProjection(columns));
		List<Object[]> rows = criteria.list();
		List<E> entities = new ArrayList<E>();

		for (Object[] objects : rows) {
			int i = 0;
			E entity = entityGenerator.getEntity();
			for (Object object : objects) {
				entitySetters[i].set(entity, typeCasters[i].convert(object));
				i++;
			}
			entities.add(entity);
		}

		return entities;

	}

	@SuppressWarnings({ "rawtypes" })
	public List executeSqlQuery(String[] columns, String tableName,
			Map<String, Object> columnNameVsSearchCriteria) {
		StringBuilder stringBuilder = new StringBuilder();

		stringBuilder.append("select ");
		if (columns == null || columns.length == 0) {
			stringBuilder.append("*");
		} else {
			stringBuilder.append(Utils.getCommaSeperatedListForQuery(columns,
					TABLE_ALIAS));
		}

		stringBuilder.append(" from ").append(tableName).append(" ")
				.append(TABLE_ALIAS).append(" where ");

		if (columnNameVsSearchCriteria != null
				& columnNameVsSearchCriteria.size() != 0) {
			for (Map.Entry<String, Object> entry : columnNameVsSearchCriteria
					.entrySet()) {
				String columnName = entry.getKey();
				stringBuilder.append(TABLE_ALIAS).append(".")
						.append(columnName);
				Object seacrhCriteria = entry.getValue();
				if (seacrhCriteria instanceof String) {
					stringBuilder.append(" like '%" + seacrhCriteria + "%'");
				} else if (seacrhCriteria instanceof Integer) {
					stringBuilder.append("=" + seacrhCriteria + " ");
				}
				stringBuilder.append(" and ");
			}
			stringBuilder.replace(stringBuilder.length() - 4,
					stringBuilder.length(), "");
		}

		List list = getSession().createQuery(stringBuilder.toString()).list();
		return list;
	}
	
	@SuppressWarnings("rawtypes")
	public List executeAggregateQuery(String query){
		List list = getSession().createQuery(query).list();
		return list;
	}
	
	public void setInitialized(){
		this.initialized = false;
		this.initSession = sessionFactory.openSession();
	}
	
	/**
	 * effectively closing the session
	 */
	public void unset(){
		this.initialized = true;
		this.initSession.close();
	}
	
	

}
